/*! \file Cliente.c
    \brief Programa cliente. Establece la comunicación con el servidor, le envía comandos, 
		solicita o envía archivos y recibe el resultado de la operacion.
    
    Detalles.
*/
#include "error_parsing.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <strings.h>
#include <unistd.h>
#include <sys/un.h>
#include <termios.h>
#include <errno.h>

#define BUFFSIZE 256
#define MAX 10000
#define TAMANIO 65000

/*! \fn int Crear_Cliente_TCP()
    \brief Crea un socket cliente de la familia INET, que hace uso del protocolo TCP
    \return El \a file \a descriptor del socket cliente creado
*/
int Crear_Cliente_TCP();

/*! \fn int Crear_Cliente_UDP(struct sockaddr_in *serv_addr)
    \brief Crea un socket cliente de la familia INET, que hace uso del protocolo UDP
    \param *serv_addr Puntero a la estructura que almacenara los datos del \a servidor
    \return El \a file \a descriptor del socket cliente creado
*/
int Crear_Cliente_UDP(struct sockaddr_in *serv_addr);

/*! \fn int Crear_Cliente_UNIX ()
    \brief Crea un socket cliente de la familia UNIX
    \return El \a file \a descriptor del socket cliente UNIX que se ha creado
*/
int Crear_Cliente_UNIX ();

/*! \fn void Recibir_Archivo(char *argumentos[ARGUMENTOS],int *fd_UDP, struct sockaddr_in *from)
    \brief Recibe un archivo ubicado en el directorio en que se encuentra el \a servidor 
		haciendo uso de socket UDP, en respuesta al comando \a get
    \param *argumentos[ARGUMENTOS] Contiene el comando ingresado y los argumentos
    \param *fd_UDP Referencia al file descriptor del socket cliente UDP
    \param *from Referencia a la estructura de datos que contiene los datos del 
		servidor del que se recibe el archivo
*/
void Recibir_Archivo(char *argumentos[ARGUMENTOS],int *fd_UDP, struct sockaddr_in *from);

/*! \fn void Enviar_Archivo(char *argumentos[ARGUMENTOS], int *fd_UDP, struct sockaddr_in *server_UDP )
    \brief Envia un archivo al directorio en que se encuentra el \a servidor 
		haciendo uso de socket UDP, en respuesta al comando \a put
    \param *argumentos[ARGUMENTOS] Contiene el comando ingresado y los argumentos
    \param *fd_UDP Referencia al file descriptor del socket cliente UDP
    \param *server_UDP Referencia a la estructura de datos que contiene los datos del servidor
*/
void Enviar_Archivo(char *argumentos[ARGUMENTOS], int *fd_UDP, struct sockaddr_in *server_UDP );

/*! \fn Ingresar_Comando(int *d_TCP, char *buffer)
    \brief Realiza el ingreso de comandos, transmitiendo caracter por caracter al servidor
    \param *d_TCP Puntero al file descriptor del socket servidor TCP
    \param *buffer Almacena el comando que se ingreso por teclado
*/
void Ingresar_Comando(int *d_TCP, char *buffer);

/*! \fn getch();
    \brief Esta función toma cada caracter presionado en modo no canonico
	\return El caracter ingresado
*/
char getch();

/**
 * En la funcion \a main se realiza la llamada a las funciones de creacion de los 
 * sockets que van a establecer la comunicacion con el proceso \a server y \a log
 * y se realiza dicha comunicacion. Transfiere los comandos ingresados al proceso
 * \a server y \a log, y el resultado de la ejecucion de estos a este ultimo proceso.
 * */
int main(int argc, char *argv[])
{
		/*--------------------------------------------------------------
		 *  				Creo socket cliente TCP
		 *------------------------------------------------------------*/
		int descriptor_TCP, descriptor_UDP, descriptor_UNIX;
		descriptor_TCP = Crear_Cliente_TCP();
		
		/*--------------------------------------------------------------
		 *  				Creo socket cliente UNIX
		 *------------------------------------------------------------*/	
		descriptor_UNIX = Crear_Cliente_UNIX();
		
		/*--------------------------------------------------------------
		 *  				Creo socket cliente UDP
		 *------------------------------------------------------------*/	
		struct sockaddr_in serv_addr_UDP;
		descriptor_UDP = Crear_Cliente_UDP(&serv_addr_UDP);

		/*--------------------------------------------------------------
		 *						Escribir y Leer
		 *------------------------------------------------------------*/
		char buffer[MAX], buffer_UNIX[BUFFSIZE];
		int terminar=0;
		
		while(terminar==0)
		{	
			int cant = 0, n;
			char *comando[ARGUMENTOS];
			
			/*	Se limpian los arreglos	*/
			for(n=0; n<ARGUMENTOS; n++) 
				comando[n]=NULL;
			bzero(buffer, MAX);
			bzero(buffer_UNIX, BUFFSIZE);
			
			/* Se ingresa comando */
			while(strlen(buffer)==0)
				Ingresar_Comando(&descriptor_TCP, buffer);
			
			/*----------------------------------------------------------
			 * 				Se comunica comando al logger
			 *--------------------------------------------------------*/
			if(write(descriptor_UNIX, buffer, strlen(buffer)) < 0)	
				error("ERROR writing to UNIX socket");
			if(read(descriptor_UNIX, buffer_UNIX, BUFFSIZE) < 0)	
				error("ERROR reading from UNIX socket");
				
			/*----------------------------------------------------------
			 * 			Se elije accion dependiendo del comando
			 *--------------------------------------------------------*/
			 Parsing(buffer, comando, &cant);
			 
			 if(((strcmp("get",comando[0]))==0) && (comando[1]!= NULL))
			 {
					Recibir_Archivo(comando, &descriptor_UDP, &serv_addr_UDP);
					tcflush(0,TCIFLUSH);
			 }
			 if(((strcmp("put",comando[0]))==0) && (comando[1]!= NULL))
			 {
					Enviar_Archivo(comando, &descriptor_UDP, &serv_addr_UDP);
					tcflush(0,TCIFLUSH);
			 }
			 if(((strcmp("bye",comando[0]))==0) && (comando[1]== NULL))
				terminar = 1;			
			
			/*----------------------------------------------------------
			 * 				 Se lee del socket server TCP
			 *--------------------------------------------------------*/
			bzero(buffer, MAX);
			bzero(buffer_UNIX, BUFFSIZE);
			
			if(read(descriptor_TCP,buffer,MAX) < 0)
				error("ERROR reading from socket");
				
			printf("%s\n", buffer);

			/*----------------------------------------------------------
			 * 				Se comunica resultado al logger
			 *--------------------------------------------------------*/
			if(strncmp(buffer,"ERROR",5)!=0)
				strncpy(buffer_UNIX, "Exito",5);
			else {
					strncpy(buffer_UNIX, buffer, strlen(buffer)-1);
			}

			if(write(descriptor_UNIX, buffer_UNIX, strlen(buffer_UNIX)) < 0)	
				error("ERROR writing to UNIX socket");
			bzero(buffer_UNIX, BUFFSIZE);
			if(read(descriptor_UNIX, buffer_UNIX, BUFFSIZE) < 0)	
				error("ERROR reading from UNIX socket");					
		}
		
		close(descriptor_UNIX);
		close(descriptor_UDP);
		close(descriptor_TCP);
  
		return 0;
 }
 
/*-------------------------------------------------------------------------------
 * 							Crea socket cliente TCP
 *------------------------------------------------------------------------------*/
int Crear_Cliente_TCP()
{
		int descriptor;
		int port_servidor = 6211;
		struct sockaddr_in serv_addr;
		struct hostent *server;

		/* Se abre el socket */
		descriptor = socket(AF_INET, SOCK_STREAM, 0);
		if (descriptor< 0) 
			error("ERROR opening socket");
		
		/* Se obtiene la informacion de la máquina local*/
		server = gethostbyname("localhost");
		if (server == NULL) {
			fprintf(stderr,"ERROR, no such host\n");
			exit(0);
		}
    
		/* Se completa la estructura con los datos del servidor */
		bzero((char *) &serv_addr, sizeof(serv_addr));
		serv_addr.sin_family = AF_INET;
		bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
		serv_addr.sin_port = htons(port_servidor);
		bzero(&(serv_addr.sin_zero), 8); 
		
		/* Se conecta el socket abierto al servidor */
		if (connect(descriptor,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) 
			error("ERROR connecting");
			
		return descriptor;	
}

/*-------------------------------------------------------------------------------
 * 							Crea socket cliente UDP
 *------------------------------------------------------------------------------*/
int Crear_Cliente_UDP(struct sockaddr_in *serv_addr)
{
	int descriptor;			
	struct hostent *he;  // para obtener nombre del host 
	
	/* Se abre el socket */
	if ((descriptor = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		error("socket");
	}
	
	/* Se obtiene la informacion de la máquina local*/
	if((he=gethostbyname("localhost"))==0)
	{
		herror("gethostbyname");
		exit(1);
	}
	
	/*Limpieza de la estructura */
	memset(&(*serv_addr), 0, sizeof( *serv_addr ) );
		 
	/* convertimos el hostname a su direccion IP */
	/* 				a donde mandar 				 */
	serv_addr -> sin_family = AF_INET; 
	serv_addr -> sin_port = htons(6210); 
	serv_addr -> sin_addr = *((struct in_addr *)he->h_addr);
	bzero(&(serv_addr->sin_zero), 8); 

	return descriptor;
}

/*-------------------------------------------------------------------------------
 * 							Crea socket cliente UNIX
 *------------------------------------------------------------------------------*/
int Crear_Cliente_UNIX()
{
	int sockfd, servlen;
	struct sockaddr_un serv_addr;

	/* Limpieza de la estructura */
	memset( (char *)&serv_addr, '\0', sizeof(serv_addr) );
	
	/* Se completa la estructura */
	serv_addr.sun_family = AF_UNIX;
	strcpy( serv_addr.sun_path, "logger_sys" );
	
	servlen = strlen( serv_addr.sun_path) + sizeof(serv_addr.sun_family);

	/* Se abre el socket */
	if ( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0) ) < 0) {
		error( "creación de socket" );
	}

	/* Se conecta el socket abierto al servidor*/
 	if ( connect( sockfd, (struct sockaddr *)&serv_addr, servlen ) < 0 ) {
		error( "conexión" );
	}
	return sockfd;	
}

/*--------------------------------------------------------------------------------
 * 							Atiende comando 'get'
 *-------------------------------------------------------------------------------*/
void Recibir_Archivo(char *argumentos[ARGUMENTOS],int *fd_UDP, struct sockaddr_in *from)
{

		char buffer_aux[BUFFSIZE];
		int tamanio_archivo;
		
		bzero( buffer_aux, BUFFSIZE );

		socklen_t tamano_direccion;
		tamano_direccion = sizeof( struct sockaddr );
	/*------------------------------------------------------------------
	 * 				Se recibe tamaño de archivo a transmitir
	 *----------------------------------------------------------------*/
		
		if(sendto ((*fd_UDP), "Listo para recibir archivo", 26, 0, (struct sockaddr *) &(*from), tamano_direccion )==-1)
		{
			error("ERROR: no se pudo enviar archivo\n");
		}
		
		if(recvfrom((*fd_UDP), buffer_aux, BUFFSIZE, 0, (struct sockaddr *)&(*from),  &tamano_direccion)==-1)
		{
			error( "lectura de socket" );
		}
		 
		/* Se verifica si en el servidor no hubo problemas al abrir el archivo */
		if(!strncmp(buffer_aux,"error",5))
			return;
	
	/*------------------------------------------------------------------
	 * 			Si el archivo a recibir existe en el servidor,
	 * 			se abre archivo para escritura en modo binario
	 * ---------------------------------------------------------------*/
		FILE *fich;
		char nombre_archivo[BUFFSIZE];
	
		bzero(nombre_archivo, BUFFSIZE);
		strncat(nombre_archivo, "copia_", 6);
		strncat(nombre_archivo, argumentos[1], strlen(argumentos[1]));
	
		fich = fopen(nombre_archivo,"wb");
		{
			if(!fich)
			{
				error("no se pudo abrir el archivo para escritura");
				return;
			}
		}
		
		tamanio_archivo = atoi(buffer_aux);
				
		char buffer_archivo[TAMANIO];
		bzero(buffer_archivo,TAMANIO);

		if(sendto ((*fd_UDP), "Recibir archivo", 15, 0, (struct sockaddr *) &(*from), tamano_direccion )==-1)
		{
			error("no se pudo enviar archivo");
		}
		
	/*------------------------------------------------------------------
	 * 						Se reciben los datos 
	 * ---------------------------------------------------------------*/
			int cant_paquetes,i, resto;
			
			/* Se calcula la cantidad de paquetes a recibir */
			cant_paquetes = (tamanio_archivo/TAMANIO)+1;
			/* Se inicializa la variable 'resto' */
			resto = tamanio_archivo;
			
			for(i=0; i<cant_paquetes; i++)
			{
				bzero(buffer_archivo,TAMANIO);
				if(resto<TAMANIO)
				{	
					/* Se recibe el ultimo paquete */
					if(recvfrom( (*fd_UDP), buffer_archivo, resto, 0, (struct sockaddr *)&(*from),  &tamano_direccion)==-1)
					{
						error( "lectura de socket" );
					}
					/* Se escribe el contenido de 'buffer_archivo' en el archivo */
					fwrite(buffer_archivo,1,resto,fich);
					
					/* Se envia confirmacion */
					if(sendto ((*fd_UDP), "Recibi archivo", 14, 0, (struct sockaddr *) &(*from), tamano_direccion )==-1)
					{
						error("ERROR: no se pudo enviar archivo");
					}			
				}
				else
				{
					/* Se recibe paquete de tamaño TAMANIO */
					if(recvfrom( (*fd_UDP), buffer_archivo, TAMANIO,  0, (struct sockaddr *)&(*from),  &tamano_direccion)==-1)
					{
						error( "lectura de socket" );
					}
					
					/* Se escribe el contenido de 'buffer_archivo' en el archivo */
					fwrite(buffer_archivo,1,TAMANIO,fich);
					
					/* Se envia confirmacion */
					if(sendto ((*fd_UDP), "Recibi datos", 12, 0, (struct sockaddr *) &(*from), tamano_direccion )==-1)
					{
						error("ERROR: no se pudo enviar archivo");
					}
					/* Se actualiza la variable 'resto' */
					resto = resto - TAMANIO;
				}	
			}

	fclose(fich);
}

/*--------------------------------------------------------------------------------
 * 							Atiende comando 'put'
 *-------------------------------------------------------------------------------*/
void Enviar_Archivo(char *argumentos[ARGUMENTOS], int *fd_UDP, struct sockaddr_in *server_UDP )
{
		FILE *fich;
		char  nombre_archivo[BUFFSIZE];
		int tamanio;
		
		bzero(nombre_archivo, BUFFSIZE);	

		/*			Abre archivo para lectura en modo binario		  */
		strncpy(nombre_archivo, argumentos[1], strlen(argumentos[1]));
		fich = fopen(nombre_archivo,"rb");
		
		/* Si ocurre un error al abrir el archivo, se comunica el codigo 
		 * de error al servidor y se retorna al programa principal     */
		if(!fich)
		{
			char cod_error[BUFFSIZE], mens_err[BUFFSIZE];
			bzero(cod_error,BUFFSIZE);
			bzero(mens_err,BUFFSIZE);
			
			sprintf(cod_error,"%d",errno);
			strncpy(mens_err, "error ",6);
			strcat(mens_err, cod_error);
			
			socklen_t tamanio_direc;
			tamanio_direc = sizeof(*server_UDP);
			if(sendto ((*fd_UDP),mens_err, strlen(mens_err), 0, (struct sockaddr *) &(*server_UDP), tamanio_direc)==-1)
			{
				error("error al enviar");
			}	
			return;	
		}
		
		/* Se determina tamaño de archivo */
		fseek(fich, 0L, SEEK_END);		
		tamanio = (int)ftell(fich);
		
		char buff_archivo[TAMANIO];		// Almacena todos los datos del archivo leido
		char buff_tamanio[BUFFSIZE];	// Se utiliza para la comunicacion con el servidor
		
		/* Ubica el puntero de posicion de fichero al inicio */
		fseek( fich, 0L, SEEK_SET );
		
		/* Se inicializa los valores del segmento de memoria en cero */
		bzero(buff_archivo, TAMANIO);
		bzero(buff_tamanio,BUFFSIZE);
		
		sprintf(buff_tamanio, "%d", tamanio);
		
		socklen_t tamanio_direc;
		tamanio_direc = sizeof(*server_UDP);
		
	/* -----------------------------------------------------------------
	 * 						Se envia tamaño del archivo 
	 * ---------------------------------------------------------------*/ 
		if(sendto ((*fd_UDP), buff_tamanio, strlen(buff_tamanio), 0, (struct sockaddr *) &(*server_UDP), tamanio_direc)==-1)
		{
			error("error al enviar");
		}	
		bzero(buff_tamanio, BUFFSIZE);
		if(recvfrom( (*fd_UDP), buff_tamanio, strlen(buff_tamanio), 0, (struct sockaddr *)&(*server_UDP),  & tamanio_direc)==-1)
		{
			error( "lectura de socket" );
		}		
		
	/*------------------------------------------------------------------
	 *					Se realiza el envio del archivo
	 *----------------------------------------------------------------*/
			
			int cant_paquetes, i, resto;
		
			/* Se calcula la cantidad de paquetes que se va a enviar */ 
			cant_paquetes = (tamanio/TAMANIO)+1;
			/* Inicializa la variable 'resto' */	
			resto = tamanio;					
			
			for(i=0; i<cant_paquetes; i++)
			{
				bzero(buff_archivo,TAMANIO);
				
				/* Si el tamaño del paquete es menor a TAMANIO, se envia el resto del archivo*/
				if(resto<TAMANIO)
				{
					/* Se serializa la parte del archivo que se va a enviar */
					fread(buff_archivo,1,resto, fich);
					if(sendto ((*fd_UDP), buff_archivo, resto, 0, (struct sockaddr *) &(*server_UDP), tamanio_direc)==-1)
					{
						error("envio archivo");
					}			
					bzero(buff_tamanio,BUFFSIZE);
					/* Se recibe confirmacion */
					if(recvfrom( (*fd_UDP), buff_tamanio, 6, 0, (struct sockaddr *)&(*server_UDP),  & tamanio_direc)==-1)
					{
						error( "lectura de socket" );
					}
				}
				/* Sino, se fragmenta el archivo en paquetes de tamaño especificado por TAMANIO y se envia
				 *  				esperando la comprobacion de su llegada								*/
				else
				{
					/* Se serializa la parte del archivo que se va a enviar */
					fread(buff_archivo,1,TAMANIO, fich);
					if(sendto ((*fd_UDP), buff_archivo, TAMANIO, 0, (struct sockaddr *) &(*server_UDP), tamanio_direc)==-1)
					{
						error("envio archivo");
					}			
					bzero(buff_tamanio,BUFFSIZE);
					/* Se recibe confirmacion */
					if(recvfrom( (*fd_UDP), buff_tamanio, 6, 0, (struct sockaddr *)&(*server_UDP),  & tamanio_direc)==-1)
					{
						error( "lectura de socket" );
					}
					
					/* Se calcula cuanto queda por transmitir */
					resto = resto - TAMANIO;
				}
			}
		fclose(fich);
}

/*---------------------------------------------------------------------------------
 * 			Realiza el ingreso de comandos comunicando al servidor
 * 					a medida que se ingresan caracteres
 *-------------------------------------------------------------------------------*/
void Ingresar_Comando(int *d_TCP, char *buffer)
{
		char caracter, comando[BUFFSIZE], respuesta[BUFFSIZE];
		int i=0;
		bzero(comando,BUFFSIZE);
		bzero(respuesta,BUFFSIZE);
		
		printf("Ingrese comando: ");
		setvbuf(stdout, NULL, _IONBF, 0);
		
		do
		{	
			/* Obtiene caracter */
			caracter = getch();
	
			/*----------------------------------------------------------
			 * 		Impresion y reconocimiento de caracter
			 * 					en cliente
			 *--------------------------------------------------------*/
			/* Anula el ingreso de los caracteres ASCII menores a 32, 
			 * salvo el que corresponde al 'enter' */
			if(caracter == 10 || caracter > 31)
			{
				/* Evita que se ingresen espacios sin antes haber ingresado cualquier otro caracter */
				if(!(caracter == 32 && i==0))
				{
					/* Reconoce codigo del 'backspace' eliminando de la pantalla el 
					 * ultimo caracter y actualizando la cadena ingresada*/
					if(caracter == 127)
					{
						if(i>0)
						{
							buffer[strlen(buffer)-1] = '\0';   
							printf("\b \b");
							i--;
						}
					}else if(caracter != '\n')
					{
						setvbuf(stdout, NULL, _IONBF, 0);
						buffer[i]= caracter;
						printf("%c",caracter);
						i++;
					}
			
					sprintf(comando, "%c", caracter);
				/*----------------------------------------------------------
				* 					Se envia caracter al servidor
				*--------------------------------------------------------*/
				if(write(*d_TCP, comando, strlen(comando)) < 0)	
					error("ERROR writing to UNIX socket");
				if(read(*d_TCP, respuesta, BUFFSIZE) < 0)	
					error("ERROR reading from UNIX socket");
				}
			}
		} while( caracter != '\n');
		if(i==0)
			bzero(buffer,BUFFSIZE);
		printf("\n");
}

/*---------------------------------------------------------------------------------
 * 				Funcion que permite el ingreso de caracteres 
 * 							en modo no canonico
 *--------------------------------------------------------------------------------*/
char getch()
{
	char buf = 0;
	struct termios old = {0};
	if (tcgetattr(0, &old) < 0)
 perror("tcsetattr()");
	old.c_lflag &= ~ICANON;
	old.c_lflag &= ~ECHO;
	old.c_cc[VMIN] = 1;
	old.c_cc[VTIME] = 0;
	if (tcsetattr(0, TCSANOW, &old) < 0)
 perror("tcsetattr ICANON");
	if (read(0, &buf, 1) < 0)
 perror ("read()");
	old.c_lflag |= ICANON;
//	old.c_lflag |= ECHO;
	if (tcsetattr(0, TCSADRAIN, &old) < 0)
 perror ("tcsetattr ~ICANON");
	return (buf);
}
